from datetime import date
from decimal import Decimal

from django.conf import settings
from django.db.models import Sum

import stripe

from apps.users.models import Provider
from apps.billing.models import Invoice
from apps.billing.types import InvoiceStatus
from apps.booking.types import BookingStatusChoices
from apps.core.types import PROVIDER_STATUS_TYPE_CHOICES
from django.utils import timezone

SMALLEST_CURRENCY_UNIT = Decimal(settings.SMALLEST_CURRENCY_UNIT)


def filter_invoices(request):
    params = request.query_params
    invoices = Invoice.objects.filter(status=InvoiceStatus.UNPAID.value, due_date__lt=timezone.now())
    if 'date' in params:
        date_filter = date.fromisoformat(params['date']).isocalendar()
        year, week = date_filter[0], date_filter[1]
        invoices = invoices.filter(created_at__year=year,
                                   created_at__week=week)

    return invoices


def charge_invoice(invoice: Invoice, check_overdue):
    provider: Provider = invoice.provider
    promo_credit = provider.promo_credit
    ethera_credit = provider.ethera_credit

    stripe_id = provider.stripe_id
    if not stripe_id:
        return
    charge_amount = invoice.total_amount

    apply_promo_credit = promo_credit if promo_credit <= charge_amount\
        else charge_amount
    charge_amount -= apply_promo_credit

    apply_ethera_credit = ethera_credit if ethera_credit <= charge_amount \
        else charge_amount
    charge_amount -= apply_ethera_credit

    canceled_amount = get_canceled_amount(invoice)

    try:
        charge = stripe_charge(stripe_id, charge_amount)
        save_invoice(
            invoice,
            charge, [
                ethera_credit,
                apply_promo_credit,
                apply_ethera_credit,
                canceled_amount
            ]
        )
        adjust_credit(
            invoice,
            [apply_promo_credit, apply_ethera_credit, canceled_amount]
        )
    except stripe.error.StripeError as ex:
        err = ex.error
        print("Code is: %s" % err.code)

        if check_overdue:
            update_overdue_invoice(invoice)

    except Exception:
        pass


def stripe_charge(account_id, amount):
    if amount <= 0:
        return None

    return stripe.Charge.create(
        amount=int(amount * SMALLEST_CURRENCY_UNIT),
        currency='usd',
        customer=account_id,
    )


# def save_manual_invoice(invoice: ManualInvoice, charge):
#     invoice.status = InvoiceStatus.PAID.value
#     invoice.payment_id = charge.id if charge is not None else None
#     invoice.save()


def save_invoice(invoice: Invoice, charge, credit_applied):
    [open_credit, promo_credit, ethera_credit,
        received_credit] = credit_applied
    invoice.status = InvoiceStatus.PAID.value
    invoice.provider_open_credit = open_credit
    invoice.applied_ethera_credit = ethera_credit
    invoice.applied_promo_credit = promo_credit
    invoice.received_credit = received_credit
    invoice.payment_id = charge.id if charge is not None else None
    invoice.save()


def update_overdue_invoice(invoice: Invoice):
    if invoice.status == InvoiceStatus.OVERDUE.value:
        Provider.objects\
            .get(id=invoice.provider)\
            .update(status=PROVIDER_STATUS_TYPE_CHOICES[2][0])  # Inactive
    invoice.status = InvoiceStatus.OVERDUE.value
    invoice.save()


def adjust_credit(invoice: Invoice, credit_applied):
    [promo_credit, ethera_credit, received_credit] = credit_applied
    provider: Provider = invoice.provider
    provider.ethera_credit += received_credit - ethera_credit
    provider.promo_credit -= promo_credit
    provider.save()


def get_canceled_amount(invoice: Invoice):
    canceled_bookings = invoice.bookings.filter(
        status=BookingStatusChoices.CANCELED.value)
    canceled_amount = canceled_bookings.aggregate(
        Sum('room_cost')).get('room_cost__sum') or 0

    return canceled_amount
